package com.wngbms.gatewaydemo;

import com.wngbms.gatewaydemo.filter.*;
import com.wngbms.gatewaydemo.handler.HttpInboundHandler;
import com.wngbms.gatewaydemo.handler.bizhandler.BaseDoHttpInboundHandler;
import com.wngbms.gatewaydemo.other.OrderedProperties;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.LinkedBlockingDeque;

@Slf4j
@ChannelHandler.Sharable
public class NettyHttpServer {

    private int port;
    private HttpInboundHandler httpInboundHandler;
    private BaseDoHttpInboundHandler baseDoHttpInboundHandler;
    private HttpRequestFilter httpRequestFilter;
    private HttpResponseFilter httpResponseFilter;

    public NettyHttpServer(int port, List<String> proxyServer, Class<?> handlerType)
            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException {
        this.port = port;

        initFilter();

        Constructor<?> constructor = handlerType.getConstructor(List.class, HttpResponseFilter.class);
        Object instance = constructor.newInstance(proxyServer, httpResponseFilter);
        baseDoHttpInboundHandler = (BaseDoHttpInboundHandler) instance;
        httpInboundHandler = new HttpInboundHandler(baseDoHttpInboundHandler, httpRequestFilter);
    }

    public void start() throws InterruptedException {

        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup(Runtime.getRuntime().availableProcessors() * 2);

        try {
            ServerBootstrap b = new ServerBootstrap();
            b.option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.TCP_NODELAY, true)
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    .childOption(ChannelOption.SO_REUSEADDR, true)
                    .childOption(ChannelOption.SO_RCVBUF, 32*1024)
                    .childOption(ChannelOption.SO_SNDBUF, 32*1024)
//                    .childOption(EpollChannelOption.SO_REUSEPORT, true)
                    .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);

            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler(LogLevel.DEBUG))
                    .childHandler(new ChannelInitializer<NioSocketChannel>() {
                        @Override
                        protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
                            ChannelPipeline pipeline = nioSocketChannel.pipeline();
                            pipeline.addLast(new HttpServerCodec());
                            pipeline.addLast(new HttpObjectAggregator(1024 * 1024));
                            pipeline.addLast(httpInboundHandler);
                        }
                    });

            Channel channel = b.bind(port).sync().channel();
            channel.closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    public void initFilter() throws IOException {
        URL resource = Thread.currentThread()
                .getContextClassLoader().getResource("filter.properties");

        Properties properties = new OrderedProperties();
        try (InputStream inputStream = resource.openStream();) {
            properties.load(inputStream);
        }

        LinkedBlockingDeque<HttpRequestFilter> requestFilterQueue = new LinkedBlockingDeque<>();
        LinkedBlockingDeque<HttpResponseFilter> responseFilterQueue = new LinkedBlockingDeque<>();

        Set<Object> objects = properties.keySet();
        for (Object object : objects) {
            String s = (String) object;
            String property = properties.getProperty(s);

            log.info("{}: {} ", s, property);

            String[] split = property.split(",");

            HttpRequestFilter lastReq = requestFilterQueue.peekLast();
            HttpResponseFilter lastResp = responseFilterQueue.peekLast();

            if (s.contains("AddRequestHeader")) {
                AddRequestHeaderFilter e = new AddRequestHeaderFilter(split[0], split[1]);
                if (lastReq != null) {
                    lastReq.setNextFilter(e);
                }
                requestFilterQueue.addLast(e);
            } else if (s.contains("AddResponseHeader")) {
                AddResponseHeaderFilter e = new AddResponseHeaderFilter(split[0], split[1]);
                if (lastResp != null) {
                    lastResp.setNextFilter(e);
                }
                responseFilterQueue.addLast(e);
            } else if (s.contains("AddRequestParameter")) {
                AddRequestParamFilter e = new AddRequestParamFilter(split[0], split[1]);
                if (lastReq != null) {
                    lastReq.setNextFilter(e);
                }
                requestFilterQueue.addLast(e);
            } else if (s.contains("RemoveRequestHeader")) {
                RemoveRequestHeaderFilter e = new RemoveRequestHeaderFilter(split[0]);
                if (lastReq != null) {
                    lastReq.setNextFilter(e);
                }
                requestFilterQueue.addLast(e);
            } else if (s.contains("RemoveResponseHeader")) {
                RemoveResponseHeaderFilter e = new RemoveResponseHeaderFilter(split[0]);
                if (lastResp != null) {
                    lastResp.setNextFilter(e);
                }
                responseFilterQueue.addLast(e);
            } else if (s.contains("RemoveRequestParameter")) {
                RemoveRequestParamFilter e = new RemoveRequestParamFilter(split[0]);
                if (lastReq != null) {
                    lastReq.setNextFilter(e);
                }
                requestFilterQueue.addLast(e);
            }
        }

        HttpRequestFilter firstReq = requestFilterQueue.peekFirst();
        HttpResponseFilter firstResp = responseFilterQueue.peekFirst();

        this.httpRequestFilter = firstReq;
        this.httpResponseFilter = firstResp;
    }

}
